package com.googlecode.horncomb.web.spring.mvc;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import static org.apache.commons.lang.Validate.*;

import org.apache.commons.lang.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver;
import org.springframework.web.servlet.view.RedirectView;

import com.googlecode.horncomb.web.extremetable.util.ExtremeTableUtil;
import com.googlecode.horncomb.web.spring.mvc.support.ServiceMethodNoFoundException;
import com.googlecode.horncomb.web.util.PageMessageFilter;

/**
 * @author Daniel
 */
@SuppressWarnings("unchecked")
public class FormServiceController<FormType extends Form, MainServiceType extends Object>
		extends EventDispatchController<FormType> {
	protected MainServiceType formService = null;
	private Map<String, Method> candidateEventServiceMethodMap = new HashMap<String, Method>();
	private Boolean paged = true;
	private Map<String, String> eventViewMap = null;

	protected FormServiceController() {
		super();
		ParameterMethodNameResolver resolver = new ParameterMethodNameResolver();
		resolver.setParamName(this.formEventFieldName);
		this.setDefaultEventHandleName("defaultEventHandle"); //$NON-NLS-1$
		resolver.setDefaultMethodName(this.defaultEventHandleName);
		// Properties logicalMappings = new Properties();
		// logicalMappings.put(Form.EVENT_REFRESH, "refresh");
		// logicalMappings.put(Form.EVENT_SAVE, "save");
		// logicalMappings.put(Form.EVENT_DELETE, "delete");
		// resolver.setLogicalMappings(logicalMappings);
		this.setMethodNameResolver(resolver);
	}

	protected Map<String, Object> referenceData(HttpServletRequest request,
			Object command, Errors errors) throws Exception {
		FormType form = convertCommandType(command);
		return this.listReference(request, form, errors);
	}

	protected Map<String, Object> listReference(HttpServletRequest request,
			FormType form, Errors errors) throws Exception {
		if ((form instanceof ListForm) && paged) {
			ListForm listForm = (ListForm) form;
			listForm.getPagedSettings().clear();
			listForm.getPagedSettings().putAll(
					ExtremeTableUtil.resolvePagedSettings(request));
		}
		return (Map<String, Object>) this.invokeEventServiceMethod(form);
	}

	public ModelAndView defaultEventHandle(HttpServletRequest request,
			HttpServletResponse response, FormType form) throws Exception {
		Object rt = this.invokeEventServiceMethod(form);
		ModelAndView mav = this.getEventView(request, response, form);
		if (rt != null) {
			try {
				if (rt instanceof Map) {
					Map<String, Object> ref = (Map<String, Object>) rt;
					mav.addAllObjects(ref);
				} else {
					this.logger.debug("ignore return value");
				}
			} catch (ClassCastException ex) {
				this.logger.debug("ignore return value", ex);
			}
		}
		return mav;
	}

	protected void addPageReturnMessage(HttpSession session, String message) {
		ArrayList<String> pageReturnMessage = (ArrayList<String>) session
				.getAttribute(PageMessageFilter.DEFAULT_MESSAGES_KEY);
		if (pageReturnMessage == null) {
			pageReturnMessage = new ArrayList();
			session.setAttribute(PageMessageFilter.DEFAULT_MESSAGES_KEY,
					pageReturnMessage);
		}
		pageReturnMessage.add(message);
	}

	protected ModelAndView resolveEventView(HttpServletRequest request,
			HttpServletResponse response, FormType form) throws Exception {
		return this.resolveResultView(request, response, form);
	}

	private Object invokeEventServiceMethod(FormType form) throws Exception {
		Method serviceMethod = this.getEventServiceMethod(form.getEvent());
		return serviceMethod.invoke(this.formService, form);
	}

	/**
	 * @param event
	 * @return not null.
	 * @throws ServiceMethodNoFoundException
	 */
	protected Method getEventServiceMethod(String event)
			throws ServiceMethodNoFoundException {
		String methodName = this.getEventServiceName(StringUtils
				.defaultIfEmpty(event, this.defaultFormEvent));
		Method serviceMethod = candidateEventServiceMethodMap.get(methodName);
		if (serviceMethod == null) {
			serviceMethod = candidateEventServiceMethodMap.get(methodName);
			if (serviceMethod == null) {
				throw new ServiceMethodNoFoundException(event, this.formService
						.getClass());
			}
		}
		return serviceMethod;
	}

	protected String getEventServiceName(String event) {
		return event;
	}

	public void setFormService(MainServiceType mainService) {
		notNull(mainService);
		this.formService = mainService;
		for (Method method : this.formService.getClass().getMethods()) {
			Class<?> returnType = method.getReturnType();
			if (!StringUtils.equals(method.getName(), "wait")
					&& !StringUtils.equals(method.getName(), "notifyAll")) {
				if (Map.class.equals(returnType)
						|| void.class.equals(returnType)) {
					if (method.getParameterTypes() == null
							|| method.getParameterTypes().length == 0
							|| Form.class.isAssignableFrom(method
									.getParameterTypes()[0])) {
						this.candidateEventServiceMethodMap.put(method
								.getName(), method);
					}
				}
			}
		}
		// // cache service method
		// this.resolveFormServiceMethods(this.formService.getClass());
	}

	// private void resolveFormServiceMethods(Class<?> clz) {
	// while (clz != null && clz.isAnnotationPresent(FormService.class)) {
	// for (Method method : clz.getMethods()) {
	// if (method.isAccessible() // is accessible
	// && method.getReturnType().equals(Map.class)
	// && method.isAnnotationPresent(EventService.class)) {
	// String[] events = method.getAnnotation(EventService.class)
	// .events();
	// if (events != null && events.length > 0) {
	// for (String event : events) {
	// this.eventServiceMethodMap.put(event, method);
	// }
	// } else {
	// this.eventServiceMethodMap
	// .put(method.getName(), method);
	// }
	// }
	// }
	// clz = clz.getSuperclass();
	// }
	// }

	private final static String PREFIX_REDIRECTVIEW = "redirect";
	private final static String VIEW_TYPE_TOKEN = ":";

	protected ModelAndView getEventView(HttpServletRequest request,
			HttpServletResponse response, FormType form) throws Exception {
		if (eventViewMap != null) {
			String viewName = eventViewMap.get(form.getEvent());
			if (StringUtils.isNotEmpty(viewName)) {
				if (StringUtils.startsWith(viewName, PREFIX_REDIRECTVIEW)) {
					String subvn = StringUtils.trimToEmpty(StringUtils
							.substring(viewName, PREFIX_REDIRECTVIEW.length()));
					if (StringUtils.startsWith(subvn, VIEW_TYPE_TOKEN)) {
						return new ModelAndView(new RedirectView(StringUtils
								.trimToEmpty(StringUtils.substring(subvn,
										VIEW_TYPE_TOKEN.length())), true));
					}
				} else {
					return new ModelAndView(viewName);
				}
			}
		}
		return super.resolveResultView(request, response, form);
	};

	public Boolean isPaged() {
		return paged;
	}

	public void setPaged(Boolean paged) {
		this.paged = paged;
	}

	/**
	 * @param eventViewMap
	 *            the eventViewMap to set
	 */
	public void setEventViewMap(Map<String, String> eventViewMap) {
		this.eventViewMap = eventViewMap;
	}
}
